#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) Greenplum Inc 2010. All Rights Reserved.
#

# System imports
import os
import sys
import signal
from optparse import OptionGroup

# import GPDB modules
try:
    from gppylib.db import dbconn
    from gppylib.gpparseopts import OptParser, OptChecker
    from gppylib.gparray import *
    from gppylib.gplog import *
    from gppylib.commands import unix, gp, base
    from gppylib.db import dbconn
    from gppylib import gparray, pgconf
    from gppylib.operations.deletesystem import validate_pgport
    from gppylib.userinput import *
    from gppylib.operations.segment_tablespace_locations import get_tablespace_locations
except ImportError, e:
    sys.exit('ERROR: Cannot import modules.  Please check that you '
             'have sourced greenplum_path.sh.  Detail: ' + str(e))

EXECNAME = os.path.split(__file__)[-1]

DEFAULT_BATCH_SIZE = 32
MAX_BATCH_SIZE = 128

_description = """

"""

_usage = """
"""


# Generic exception for all things activatestandby
class GpDeleteSystemException(Exception):
    pass


# -------------------------------------------------------------------------
# parseargs() - parses and validates command line args
# -------------------------------------------------------------------------
def parseargs():
    parser = OptParser(option_class=OptChecker,
                       description=' '.join(_description.split()),
                       version='%prog version $Revision$')
    parser.setHelp([])
    parser.remove_option('-h')
    parser.remove_option('--version')

    # General options section
    optgrp = OptionGroup(parser, 'General options')
    optgrp.add_option('-?', '--help', dest='help', action='store_true',
                      help='Display this help message and exit')
    optgrp.add_option('-v', '--version', dest='version', action='store_true',
                      help='Display version information and exit.')
    parser.add_option_group(optgrp)

    # Logging options section
    optgrp = OptionGroup(parser, 'Logging options')
    optgrp.add_option('-l', '--logfile', type='string', default=None,
                      help='Alternative log file directory')
    optgrp.add_option('-D', '--verbose', help='Enable debug logging.',
                      dest='verbose', default=False, action='store_true')
    parser.add_option_group(optgrp)

    # Standby activation options section
    optgrp = OptionGroup(parser, 'Delete system options')
    optgrp.add_option('-d', '--master-data-directory', dest='master_data_dir',
                      type='string', help='Master data directory.')
    optgrp.add_option('-f', '--force', action='store_true',
                      help='Force deletion.  Ignore any database backup files.')
    optgrp.add_option('-B', '--batch-size', type='int', dest='batch_size',
                      default=DEFAULT_BATCH_SIZE,
                      help='Number of batches to run in parallel. (Default %s)' % DEFAULT_BATCH_SIZE)
    parser.add_option_group(optgrp)

    # Parse the command line arguments
    (options, args) = parser.parse_args()

    if options.help:
        parser.print_help()
        parser.exit(0, None)

    if options.version:
        parser.print_version()
        parser.exit(0, None)

    # check we got the -d option
    if not options.master_data_dir:
        logger.info('Option -d or --master-data-directory not set. Checking environment variable MASTER_DATA_DIRECTORY')
        env_master_data_dir = os.getenv('MASTER_DATA_DIRECTORY', None)
        # check for environment variable MASTER_DATA_DIRECTORY
        if not env_master_data_dir:
            logger.fatal('Both -d parameter and MASTER_DATA_DIRECTORY environment variable are not set.')
            logger.fatal('Required option master data directory is missing.')
            parser.exit(2, None)
        options.master_data_dir = env_master_data_dir

    # We have to normalize this path for a later comparison
    options.master_data_dir = os.path.normpath(options.master_data_dir)

    # Check that master data directory exists
    if not os.path.exists(options.master_data_dir) or not os.path.isdir(options.master_data_dir):
        logger.fatal('Master data directory supplied %s does not exist' % options.master_data_dir)
        parser.exit(2, None)

    if options.logfile and not os.path.exists(options.logfile):
        logger.fatal('Log directory %s does not exist.' % options.logfile)
        parser.exit(2, None)

    options.pgport = validate_pgport(options.master_data_dir)

    # Set log level
    if options.verbose:
        enable_verbose_logging()

    # verify batch size
    if options.batch_size < 1 or options.batch_size > MAX_BATCH_SIZE:
        logger.fatal('--batch-size value must be from 1 to %s' % MAX_BATCH_SIZE)
        parser.exit(2, None)

    # There shouldn't be any args
    if len(args) > 0:
        logger.error('Unknown arguments:')
        for arg in args:
            logger.error('  %s' % args)
        parser.exit(2, None)

    return options, args


# -------------------------------------------------------------------------
# display_params() - displays delete system parameters.
# -------------------------------------------------------------------------
def display_params(options, dburl, standby, segments, dumpDirsExist):
    global g_warnings_generated
    logger.info('Greenplum Instance Deletion Parameters')
    logger.info('--------------------------------------')
    logger.info('Greenplum Master hostname                  = %s' % dburl.pghost)
    logger.info('Greenplum Master data directory            = %s' % options.master_data_dir)
    logger.info('Greenplum Master port                      = %s' % dburl.pgport)
    if standby:
        logger.info('Greenplum Master standby host              = %s' % standby.getSegmentHostName())
        logger.info('Greenplum Master standby data directory    = %s' % standby.getSegmentDataDirectory())
        logger.info('Greenplum Master standby port              = %s' % standby.getSegmentPort())
    if options.force:
        logger.info('Greenplum Force delete of dump files       = ON')
    else:
        logger.info('Greenplum Force delete of dump files       = OFF')
    logger.info('Batch size                                 = %s' % options.batch_size)
    logger.info('--------------------------------------')
    logger.info(' Segment Instance List ')
    logger.info('--------------------------------------')
    logger.info('Host:Datadir:Port')
    for segdb in segments:
        host = segdb.getSegmentHostName()
        datadir = segdb.getSegmentDataDirectory()
        port = segdb.getSegmentPort()
        logger.info('%s:%s:%s' % (host, datadir, port))

    yn = ask_yesno('', 'Continue with Greenplum instance deletion?', 'N')
    if yn:
        logger.info('FINAL WARNING, you are about to delete the Greenplum instance')
        logger.info('on master host %s.' % dburl.pghost)
        if dumpDirsExist and options.force:
            logger.warn('There are database dump files, these will be DELETED if you continue!')
            g_warnings_generated = True
        yn = ask_yesno('', 'Continue with Greenplum instance deletion?', 'N')
        if not yn:
            raise GpDeleteSystemException('User canceled')
    else:
        raise GpDeleteSystemException('User canceled')


# -------------------------------------------------------------------------
# check_for_dump_files() - checks if there are database dump files
# -------------------------------------------------------------------------
def check_for_dump_files(options):
    logger.info('Checking for database dump files...')
    return gp.GpDirsExist.local('check for dump dirs', baseDir=options.master_data_dir, dirName="'*dump*'") \
           or gp.GpDirsExist.local('check for dump dirs', baseDir=options.master_data_dir, dirName="'*backups*'")

# -------------------------------------------------------------------------
# delete_system() - deletes a GPDB system
# -------------------------------------------------------------------------
def delete_cluster(options):
    global g_warnings_generated

    # check for dumps if needed
    dump_files_exist = check_for_dump_files(options)
    if not options.force and dump_files_exist:
        logger.fatal('Located possible database backup file on Master instance host')
        logger.fatal('in directory %s' % options.master_data_dir)
        logger.fatal('To override database backup file checking use -f option')
        raise GpDeleteSystemException('Backup files exist')

    # get gparray object
    logger.info('Getting segment information...')
    dburl = dbconn.DbURL(port=options.pgport)
    try:
        array = gparray.GpArray.initFromCatalog(dburl, True)
    except Exception, ex:
        raise GpDeleteSystemException('Failed to get database configuration: %s' % ex.__str__())

    #get tablespace locations of segments from all hosts
    tablespace_locations = get_tablespace_locations(True, None)

    # get all segdbs
    segments = array.getDbList()

    standby = array.standbyMaster

    # Display the options
    display_params(options, dburl, standby, segments, dump_files_exist)

    # stop database
    logger.info('Stopping database...')
    try:
        cmd = gp.GpStop('stop database', fast=True, datadir=options.master_data_dir)
        cmd.run(validateAfter=True)
    except:
        results = cmd.get_results()
        if results.rc > 1:
            raise GpDeleteSystemException('Failed to stop database.')
        logger.warn('Warnings were generated while stopping the database.')
        g_warnings_generated = True

    try:
        # From this point on we don't want ctrl-c to happen
        signal.signal(signal.SIGINT, signal.SIG_IGN)

        # create pool
        pool = base.WorkerPool(numWorkers=options.batch_size)

        try:
            if tablespace_locations:
                logger.info('Deleting tablespace directories...')
                for host, tablespace_dir in tablespace_locations:
                    logger.debug('Queueing up command to remove %s:%s' % (host, tablespace_dir))
                    cmd = unix.RemoveDirectory('remove tablespace dir', tablespace_dir,
                                               ctxt=base.REMOTE, remoteHost=host)
                    pool.addCommand(cmd)

                logger.info('Waiting for worker threads to delete tablespace dirs...')
        finally:
            pool.join()
            pool.haltWork()
            pool.joinWorkers()

        # create pool
        pool = base.WorkerPool(numWorkers=options.batch_size)

        try:
            logger.info('Deleting segments and removing data directories...')
            for segdb in segments:
                segmentDataDirectory = segdb.getSegmentDataDirectory()
                logger.debug('Queueing up command to remove %s:%s' % (segdb.getSegmentHostName(),
                                                                      segmentDataDirectory))
                cmd = unix.RemoveDirectory('remove data dir', segmentDataDirectory, ctxt=base.REMOTE,
                                           remoteHost=segdb.getSegmentHostName())
                pool.addCommand(cmd)

            logger.info('Waiting for worker threads to complete...')
        finally:
            pool.join()
            pool.haltWork()
            pool.joinWorkers()

    finally:
        # Re-enable ctrl-c
        signal.signal(signal.SIGINT, signal.default_int_handler)

#############
if __name__ == '__main__':
# -------------------------------------------------------------------------
# main
# -------------------------------------------------------------------------

    g_warnings_generated = False
    g_errors_generated = False

    # setup logging
    logger = get_default_logger()
    setup_tool_logging(EXECNAME, unix.getLocalHostname(), unix.getUserName())

    # parse args and options
    (options, args) = parseargs()

    # if we got a new log dir, we can now set it up.
    if options.logfile:
        setup_tool_logging(EXECNAME, unix.getLocalHostname(), unix.getUserName(), logdir=options.logfile)

    try:
        # save off cwd
        cwd = os.getcwd()
        # chdir to home to prevent from trying to delete a dir while we are in it
        home = os.getenv('USERPROFILE') or os.getenv('HOME')
        os.chdir(home)

        # do the delete
        delete_cluster(options)

        # now try to go back into the original cwd
        try:
            os.chdir(cwd)
        except:
            # we can ignore this as cwd must no longer exist
            pass

    except GpDeleteSystemException, ex:
        g_errors_generated = True
        if ex.__str__() == 'User canceled':
            logger.info(ex.__str__())
        else:
            logger.fatal(ex.__str__())
        if options.verbose:
            logger.exception(ex)
    except Exception, ex:
        g_errors_generated = True
        logger.fatal('Error deleting system: %s' % ex.__str__())
        if options.verbose:
            logger.exception(ex)
    finally:
        if g_errors_generated:
            logger.error('Delete system failed')
            sys.exit(2)
        elif g_warnings_generated:
            logger.warn('Delete system completed but warnings were generated.')
            sys.exit(1)
        else:
            logger.info('Delete system successful.')
            sys.exit(0)
